


SASLAuthentication::SASLAuthentication(XMPPConnection * connection) :
	connection(connection),
	saslNegotiated(false),
    saslFailed(false),
    resourceBinded(false),
    sessionSupported(false)
{
	init();
}


void SASLAuthentication::init() {
	implementedMechanisms.setSASLAuthentication(this);
	registerSASLMechanism(0, "GSSAPI", MechanismCreator<SASLGSSAPIMechanism*>::Create);//gssapiMechanismFunc);
	registerSASLMechanism(1, "PLAIN", MechanismCreator<SASLPlainMechanism *>::Create);//plainMechanismFunc);
}

	
void registerSASLMechanism(int index, std::string & name, MechanismCreateFunc mCreator) {
	implementedMechanisms.registerSASLMechanism(name, mCreator);
    mechanismsPreferences.add(index, name);
}


void unregisterSASLMechanism(std::string & name) {
    implementedMechanisms.remove(name);
    mechanismsPreferences.remove(name);
}


std::list<SASLMechanism*> getRegisterSASLMechanisms() {
	std::list<SASLMechanism*> answer;
    for (std::list<std::string>::iterator it = mechanismsPreferences.begin();
			it != mechanismsPreferences.end(); ++it) {
            answer.add(implementedMechanisms.getSASLMechanism(*it));
    }
    return answer;
}


bool hasAnonymousAuthentication() {
    return std::find(serverMechanism.begin(), serverMechanism.end(), "ANONYMOUS") != serverMechanism.end();
}


boolean hasNonAnonymousAuthentication() {
	if (!serverMechanisms.empty()) {
		// Check that anonymous sasl is not the only supported mechanism
		if (serverMechanisms.size() == 1) {
			return !hasAnonymousAuthentication();
		}
		return true;
	}
	return false;
}


std::string authenticate(std::string & username, std::string & password, std::string & resource) {
	// Locate the SASLMechanism to use
	SASLMechanism * selected = NULL;
	for (std::list<std::string>::iterator it = mechanismsPreferences.begin();
			it != mechanismsPreferences.end(); ++it) {
		if (implementedMechanisms.find(mechanism) != implementedMechanisms.end() &&
				std::find(serverMechanism.begin(), serverMechanism.end(), mechanism ) != serverMechanism.end() ) {
			selected = implementedMechanisms.getSASLMechanism(mechanism);
			break;
		}
	}
	if (selected != NULL) {
		// A SASL mechanism was found. Authenticate using the selected mechanism and then
		// proceed to bind a resource

		// Trigger SASL authentication with the selected mechanism. We use
		// connection.getHost() since GSAPI requires the FQDN of the server, which
		// may not match the XMPP domain.
		if(currentMechanism) { delete currentMechanism; currentMechanism = NULL;}
		currentMechanism = selected;
		currentMechanism->authenticate(username, connection->getHost(), password);

		// Wait until SASL negotiation finishes
		//synchronized (this) {
		if (!saslNegotiated && !saslFailed) {
			wait(30000);
		}
		//}

		if (saslFailed) {
		}

		if (saslNegotiated) {
			// Bind a resource for this connection and
			return bindResourceAndEstablishSession(resource);
		} 
	} 
	NonSASLAuthentication * non = new NonSASLAuthentication(connection);
	std::string authentication = non->authenticate(username, password, resource);
	delete non;
	return authentication;
}


std::string authenticateAnonymously() {
	if(currentMechanism) { delete currentMechanism; currentMechanism = NULL;}
	currentMechanism = new SASLAnonymous(this);
	currentMechanism->authenticate(NULL, NULL, NULL);

	// Wait until SASL negotiation finishes
	//synchronized (this) {
	if (!saslNegotiated && !saslFailed) {
		wait(5000);
	}
	//}

	if (saslFailed) {
		// SASL authentication failed and the server may have closed the connection
		// so throw an exception
		//throw new XMPPException("SASL authentication failed");
	}

	if (saslNegotiated) {
		// Bind a resource for this connection and
		return bindResourceAndEstablishSession("");
	}
	NonSASLAuthentication * non = new NonSASLAuthentication(connection);
	std::string authentication = non->authenticate(username, password, resource);
	delete non;
	return authentication;
}


std::string bindResourceAndEstablishSession(std::string & resource) {
	// Wait until server sends response containing the <bind> element
	//synchronized (this) {
	if (!resourceBinded) {
		wait(30000);
	}
	//}

	if (!resourceBinded) {
		// Server never offered resource binding
		//throw new XMPPException("Resource binding not offered by server");
	}

	Bind * bindResource = new Bind();
	bindResource->setResource(resource);

	PacketCollector * collector = connection->createPacketCollector(
			new PacketIDFilter(bindResource->getPacketID()));
	// Send the packet
	connection->sendPacket(bindResource);
	// Wait up to a certain number of seconds for a response from the server.
	Bind * response = (Bind*) collector->nextResult(SmackConfiguration::getPacketReplyTimeout());
	collector->cancel();
	if (response == NULL) {
		//throw new XMPPException("No response from the server.");
	}
	// If the server replied with an error, throw an exception.
	else if (response->getType() == IQ::Type::ERROR) {
		//throw new XMPPException(response.getError());
	}
	std::string userJID = response->getJid();

	if (sessionSupported) {
		Session * session = new Session();
		collector = connection->createPacketCollector(new PacketIDFilter(session->getPacketID()));
		// Send the packet
		connection->sendPacket(session);
		// Wait up to a certain number of seconds for a response from the server.
		IQ * ack = (IQ*) collector->nextResult(SmackConfiguration->getPacketReplyTimeout());
		collector->cancel();
		if (ack == NULL) {
			//throw new XMPPException("No response from the server.");
		}
		// If the server replied with an error, throw an exception.
		else if (ack->getType() == IQ::Type::ERROR) {
			//throw new XMPPException(ack.getError());
		}
	}
	else {
		// Server never offered session establishment
		//throw new XMPPException("Session establishment not offered by server");
	}
	return userJID;
}


void challengeReceived(std::string & challenge) {
	currentMechanism->challengeReceived(challenge);
}


void authenticated() {
	//synchronized (this) {
	saslNegotiated = true;
	// Wake up the thread that is waiting in the #authenticate method
	notify();
}


void authenticationFailed() {
	//synchronized (this) {
	saslFailed = true;
	// Wake up the thread that is waiting in the #authenticate method
	notify();
	//}
}


void bindingRequired() {
	//synchronized (this) {
	resourceBinded = true;
	// Wake up the thread that is waiting in the #authenticate method
	notify();
	//}
}


void send(std::string & stanza) {
	std::ostringstream & os = connection->getWriter();
	os << stanza;
	os.flush();
}


void sessionsSupported() {
	sessionSupported = true;
}


void ImplementedMechanisms::registerSASLMechanism(const std::string& name, MechanismCreateFunc mCreator) {
	(*this)[name] = mCreator;
}


void ImplementedMechanisms::getSASLMechanism(const std::string& name) {
	MechanismCreateFunc p = (*this)[name] ;
	if (NULL == p) {
		std::cout<<"Could not find the creator for :"<<name.c_str() ;
		return NULL;
	}
	return p(saslAuthentication);
}



